package com.ksd.common.aspect;

import com.ksd.common.annotation.ApiLog;
import com.ksd.common.annotation.LoginIgnore;
import com.ksd.common.core.response.ResponseEnum;
import com.ksd.common.core.result.R;
import com.ksd.common.core.result.utils.JsonUtil;
import com.ksd.common.web.model.SysUser;
import com.ksd.common.web.model.SystemInfo;
import com.ksd.common.web.threadlocal.SystemIdThrealLocal;
import com.ksd.common.web.threadlocal.UserIdThrealLocal;
import com.ksd.modules.adminlogs.po.SysLogSavePO;
import com.ksd.modules.adminlogs.service.ISysLogService;
import com.ksd.modules.rbac.sysuser.entity.SysUsers;
import com.ksd.modules.rbac.sysuser.po.UserLoginPO;
import com.ksd.modules.rbac.sysuser.service.ISysUsersService;
import com.ksd.modules.system.entity.AdminSystem;
import com.ksd.modules.system.service.IAdminSystemService;
import com.ksd.utils.ip.IpUtils;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 统一的登录拦截校验切面逻辑实现类
 */
@Component
@Aspect
@Slf4j
public class AdminGlobalLoginAspect {

    /**
     * 请求头登录认证key
     */
    private static final String LOGIN_AUTH_REQUEST_HEADER_NAME = "Authorization";
    private static final String LOGIN_AUTH_REQUEST_HEADER_UID = "Uid";
    private static final String SYSTEM_ID = "SystemId";

    /**
     * 切点表达式com.ksd.modules.content.advert.controller
     */
    private final static String POINT_CUT = "execution(* com.ksd.modules.**.controller..*(..))";

    @Autowired
    private ISysLogService saveSystemLog;
    @Autowired
    private ISysUsersService sysUsersService;
    @Autowired
    private IAdminSystemService adminSystemService;

    /**
     * 切点模版方法
     */
    @Pointcut(value = POINT_CUT)
    public void loginAuth() {

    }

    /**
     * 切点的环绕增强逻辑
     * 1、需要判断需不需要校验登录信息
     * 2、校验登录信息：
     * a、获取token 从请求头或者参数
     * b、从缓存中获取token，进行比对
     * c、解析token
     * d、解析的userId存入线程上下文，供下游使用
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("loginAuth()")
    public Object loginAuthAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        HttpServletResponse response = servletRequestAttributes.getResponse();
        if (checkNeedCheckLoginInfo(proceedingJoinPoint)) {
            String requestURI = request.getRequestURI();
            log.info("成功拦截到请求，URI为：{}", requestURI);
            if (!checkAndSaveUserId(request, response)) {
                log.warn("成功拦截到请求，URI为：{}. 检测到用户未登录，将跳转至登录页面", requestURI);
                return R.fail(ResponseEnum.NEED_LOGIN);
            }
            log.info("成功拦截到请求，URI为：{}，请求通过", requestURI);
            // 系统存放
            saveSystemId(request);
            long start = System.currentTimeMillis();
            Object proceed = proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis() - start;
            saveSystemLog(request, proceedingJoinPoint, end);
            return proceed;
        }

        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    }

    private void saveSystemLog(HttpServletRequest request, ProceedingJoinPoint joinPoint, long time) {
        SysLogSavePO sysLogSavePO = new SysLogSavePO();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 获取执行方法
        Method method = methodSignature.getMethod();
        if (Objects.nonNull(method)) {
            // 获取方法注解
            ApiLog apiLog = method.getDeclaredAnnotation(ApiLog.class);
            if (Objects.nonNull(apiLog)) {
                // 获取切入点方法的名字
                String methodName = methodSignature.getName();
                //获取方法的参数
                Object[] args = joinPoint.getArgs();
                // 获取切入点所在目标对象
                String className = joinPoint.getTarget().getClass().getSimpleName();

                String account = UserIdThrealLocal.get().getAccount();
                if (methodName.equals("login")) {
                    UserLoginPO userLogin = (UserLoginPO) args[0];
                    account = userLogin.getAccount();
                }

                String userAgent = request.getHeader("User-Agent");
                UserAgent ua = UserAgent.parseUserAgentString(userAgent);
                Browser browser = ua.getBrowser();
                OperatingSystem os = ua.getOperatingSystem();

                sysLogSavePO.setSystemId(SystemIdThrealLocal.get().getSystemId());
                sysLogSavePO.setSystemName(SystemIdThrealLocal.get().getSystemName());
                sysLogSavePO.setClassName(className);
                sysLogSavePO.setMethod(methodName);
                sysLogSavePO.setParams(Objects.isNull(args) ? "" : JsonUtil.obj2String(args));
                sysLogSavePO.setRequestIp(IpUtils.getIpAddress(request));
                sysLogSavePO.setBrowser(browser.getName());
                sysLogSavePO.setBrowserVersion(browser.getVersion(userAgent).getVersion());
                sysLogSavePO.setOs(os.getName());
                sysLogSavePO.setUsername(account);
                sysLogSavePO.setAddress(request.getRequestURI());
                sysLogSavePO.setTime(time);
                sysLogSavePO.setDescription(apiLog.value());
                saveSystemLog.saveUpdateSysLog(sysLogSavePO);
            }
        }
    }

    /**
     * 校验token并提取userId
     *
     * @param request
     * @return
     */
    private boolean checkAndSaveUserId(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader(LOGIN_AUTH_REQUEST_HEADER_NAME);
        String uid = request.getHeader(LOGIN_AUTH_REQUEST_HEADER_UID);

        if (StringUtils.isBlank(token)) {
            return false;
        }

        if (StringUtils.isBlank(uid)) {
            return false;
        }

        // 放入到本地缓存中
        saveUserId(Long.parseLong(uid));

        return true;
    }

    /**
     * 保存用户ID到线程上下文中
     *
     * @param userId
     */
    private void saveUserId(Object userId) {
        SysUsers sysUsers = sysUsersService.getSysUsersById(Long.parseLong(String.valueOf(userId)));
        if (sysUsers != null) {
            SysUser sysUser = new SysUser();
            sysUser.setUserId(sysUsers.getId());
            sysUser.setUsername(sysUsers.getUsername());
            sysUser.setAccount(sysUsers.getAccount());
            sysUser.setAvatar(sysUsers.getAvatar());
            UserIdThrealLocal.set(sysUser);
        }
    }

    /**
     * 默认系统
     */
    private void saveSystemId(HttpServletRequest request) {
        String systemId = request.getHeader(SYSTEM_ID);
        AdminSystem adminSystemDefault = adminSystemService.getAdminSystemById(Long.parseLong(systemId));
        if (adminSystemDefault == null) {
            adminSystemDefault = adminSystemService.getAdminSystemDefault();
        }

        if (adminSystemDefault != null) {
            SystemIdThrealLocal.set(new SystemInfo(adminSystemDefault.getId(), adminSystemDefault.getSysname(), adminSystemDefault.getDomain()));
        }
    }

    /**
     * 校验是否需要校验登录信息
     *
     * @param proceedingJoinPoint
     * @return true 需要校验登录信息 false 不需要
     */
    private boolean checkNeedCheckLoginInfo(ProceedingJoinPoint proceedingJoinPoint) {
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        return !method.isAnnotationPresent(LoginIgnore.class);
    }
}
